iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
Software Development

一起看無間道學EdgeDB系列 第 14

[Day14] - 首幕:韓琛初現

  • 分享至 

  • xImage
  •  

Full schema preview

本日所有schema搶先看

劇情提要

韓琛準備派遣多個身家較為清白的小弟臥底至香港警隊,包括建明。他向小弟們講述著自己的過去,並說自己不相信算命先生所說的「一將功成萬骨枯」。他認為出來混的,未來的路怎麼走應該由自己決定。

scene01
此劇照引用自IMDb-無間道Ⅲ終極無間

EdgeQL query

執行初始migration

在開始進行所有query前,我們需要先告知EdgeDB初始的schema。

  • 如果您是在命令列,需要輸入edgedb migration create後,再輸入edgedb migrate
  • 如果您是在EdgeDB REPL,可以使用更便捷的\migration create\migrate指令。

insert此場景時間1992年

insert FuzzyTime {fuzzy_year:= 1992};

insert韓琛及其演員曾志偉

韓琛於開頭就說出「一將功成萬骨枯」的經典句,我們將此句收錄在classic_lines property中。

此外,雖然actorsmulti link,可以包括多個演員。但是我們可以使用assert_single()來確保最多只會接收到一個曾志偉Actor object。這麼一來,如果資料庫內已經有兩個Actor objectname都叫曾志偉時,這個query就會報錯。

insert Actor {
     name:= "曾志偉",
     eng_name:= "Eric",
     nickname:= "獎老",
};

insert GangsterBoss {
    name:= "韓琛",
    nickname:= "琛哥",
    classic_lines:= ["一將功成萬骨枯"],
    actors := assert_single((select Actor filter .name = "曾志偉")),
};

另一種作法是觀察想選擇的object是否有constraint exclusiveproperty可以作為filter。如果有的話,即代表我們最多只會選擇到一個object,此時就不需要額外使用assert_single()了。這裡由於Actor object沒有constraint exclusiveproperty,所以無法使用這個作法。

insert劉建明及其少年時期演員陳冠希

insert Actor {
    name:= "陳冠希",
    eng_name:= "Edison",
};

insert GangsterSpy {
   name:= "劉建明",
   nickname:= "劉仔",
   gangster_boss:= assert_single((select GangsterBoss filter .name = "韓琛")),
   dept:= "警校學生",
   actors := assert_single((select Actor filter .name in {"陳冠希"})),
};

語法與前面類似,留意filter時也可以試試用in {}的寫法。

建立alias

由於每次都要使用(select ... filter ... .xxx=ooo)的語法來選擇object頗為麻煩,針對常用到的object,可以直接在schema中建立alias,方便取用。我們這邊定義了一個hon(韓琛)、lau(劉建明)及year_1992(1992年)的alias

alias hon:= assert_exists(
                assert_single(
                    (select GangsterBoss filter .name = "韓琛")
                )
);

alias lau:= assert_exists(
                assert_single(
                    (select GangsterSpy filter .name = "劉建明")
                )
);

alias year_1992:= assert_exists(assert_single((select FuzzyTime 
                                    filter .fuzzy_year = 1992 
                                    and .fuzzy_month ?= <FuzzyMonth>{}
                                    and .fuzzy_day ?= <FuzzyDay>{}
                                    and .fuzzy_hour ?= <FuzzyHour>{}
                                    and .fuzzy_minute ?= <FuzzyMinute>{}
                                    and .fuzzy_second ?= <FuzzySecond>{}   
                                    and .fuzzy_dow ?= <DayOfWeek>{}
                    ))
);

year_1992中,我們用到了?=operator?=除了像=可以比較兩個set外,還可以比較空set。當兩個set都為空時,會返回true。當有些property可以為空set時,?=是個非常好用的工具。另外,別忘了空EdgeDBset需要定義型別。

alias year_1992的另一種寫法也可以使用fuzzy_fmt這個computed property來作為filter的條件:

alias year_1992:= assert_exists(
                    assert_single(
                        (
                            select FuzzyTime 
                            filter .fuzzy_fmt="1992/MM/DD_HH24:MI:SS_ID"
                        )
                    )
);

這種寫法比較快速,是我實際寫query會用的方法。但在定義schema時,我反而比較喜歡原先那種直接了當的寫法。

編寫測試aliasfunction

您可能有留意到,我們在alias前都加上了assert_exists()assert_single(),這樣可以確保每個alias只會返回剛好一個object。我自己會習慣寫一個名為test_alias()function來做測試:

function test_alias() -> bool
using (all({
        test_scene01_alias(),
    })
);

function test_scene01_alias() -> bool
using (all({
        (exists hon),          
        (exists lau),
        (exists year_1992),   
    })
);

另外提醒習慣寫Python的朋友,定義function時,在()後不需要加上:

test_alias()中會包含多個場景的sub-test(如test_scene01_alias()),當每一個場景的sub-test都返回true時,all會返回true,否則會報錯。而我們利用exists檢查各場景中的alias是否存在,如果全部都存在的話,all會返回true,否則會報錯:

edgedb error: CardinalityViolationError: assert_exists violation: expression returned an empty set

這麼一來當我們在操作資料庫時,可以隨時透過test_alias()來確認每一個alias,是否都如預期地返回了剛好一個object

執行end migration

did you create alias 'default::hon'? [y,n,l,c,b,s,q,?]
> y
did you create alias 'default::lau'? [y,n,l,c,b,s,q,?]
> y
did you create alias 'default::year_1992'? [y,n,l,c,b,s,q,?]
> y
did you create function 'default::test_scene01_alias'? [y,n,l,c,b,s,q,?]
> y
did you create function 'default::test_alias'? [y,n,l,c,b,s,q,?]
> y

測試test_alias()

# end migration needs to be applied before running this query
select test_alias();

如果上述query可以成功執行,就代表我們定義的alias都沒有問題。

insert此場景的Scene

因為剛剛已經透過test_alias()測試了所有alias,所以這裡我們可以放心使用。
值得注意的是,我們在這裡使用nested insert,同時insertScene object佛堂這個Location object

insert Scene {
    title:= "韓琛初現",
    detail:= "韓琛準備派遣多個身家較為清白的小弟臥底至香港警隊,包括建明。" ++
             "他向小弟們講述著自己的過去,並說自己不相信算命先生所說的" ++
             "「一將功成萬骨枯」。他認為出來混的,未來的路怎麼走應該由自己決定。",
    remarks:= "1.假設此場景為1992年。",  
    who:= {hon, lau},
    `when`:= year_1992,
    where:= (insert Location {name:= "佛堂"}) ,   
    references:= [
      (
          "維基百科-無間道", 
          "https://zh.wikipedia.org/zh-tw/%E7%84%A1%E9%96%93%E9%81%93"
      ),
      (
          "香港警察職級", 
          "https://zh.wikipedia.org/zh-tw/%E9%A6%99%E6%B8%AF%E8%AD%A6%E5%AF%9F%E8%81%B7%E7%B4%9A"
      )
   ]
};

無間假設

理論上,我們應該處理建明由Gangster object轉變到GangsterSpy object的過程,但這對於首幕來說,可能太過複雜,所以在此處直接insert建明為GangsterSpy object。同理,我們將於次幕直接insert永仁為PoliceSpy object,而不處理其由Police object轉變到PoliceSpy object的過程。

無間吹水

佛堂前六個骨灰罈暗指當年韓琛死於屯門的六個兄弟。他藉祭拜為由,於一眾小弟面前展現其「仁義之風」。

參考資料

無間EdgeDB首幕:韓琛初現


上一篇
[Day13] - 初始schema:事
下一篇
[Day15] - 次幕:我想跟他換
系列文
一起看無間道學EdgeDB30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言